/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.nodes; import java.awt.Image; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.beans.*; import java.beans.beancontext.BeanContext; import java.beans.beancontext.BeanContextChild; import java.beans.beancontext.BeanContextProxy; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.BufferedOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ResourceBundle; import java.util.ArrayList; import org.openide.util.datatransfer.NewType; import org.openide.util.datatransfer.PasteType; import org.openide.util.datatransfer.ExTransferable; import org.openide.TopManager; import org.openide.NotifyDescriptor; import org.openide.loaders.DataFolder; import org.openide.actions.CopyAction; import org.openide.actions.CustomizeBeanAction; import org.openide.actions.PropertiesAction; import org.openide.actions.ToolsAction; import org.openide.cookies.InstanceCookie; import org.openide.filesystems.*; import org.openide.loaders.InstanceSupport; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.RequestProcessor; import org.openide.util.WeakListener; import org.openide.util.actions.SystemAction; import org.openide.explorer.propertysheet.editors.NodeCustomizer; /** Represents one JavaBean in the nodes hierarchy. * It provides all methods that are needed for communication between * the IDE and the bean. * <p>You may use this node type for an already-existing JavaBean (possibly * using BeanContext) in order for its JavaBean properties to be reflected * as corresponding node properties. Thus, it serves as a compatibility wrapper. * * @author Jan Jancura, Ian Formanek, Jaroslav Tulach */ public class BeanNode extends AbstractNode { // static .................................................................................................................. // [PENDING] use same consts in Sheet instead --jglick /** Names of propertySets. */ private static final String PROPERTIES = "properties"; // NOI18N private static final String EXPERT = "expert"; // NOI18N /** Icon base for bean nodes */ private static final String ICON_BASE = "/org/netbeans/core/resources/beans"; // NOI18N private static Children getChildren (Object bean) { if (bean instanceof BeanContext) return new BeanChildren ((BeanContext)bean); if (bean instanceof BeanContextProxy) { BeanContextChild bch = ((BeanContextProxy)bean).getBeanContextProxy(); if (bch instanceof BeanContext) return new BeanChildren ((BeanContext)bch); } return Children.LEAF; } // variables ............................................................................................................. /** bean */ private Object bean; /** bean info for the bean */ private BeanInfo beanInfo; /** functions to operate on beans */ private Method nameGetter = null; private Method nameSetter = null; /** remove PropertyChangeListener method */ private Method removePCLMethod = null; /** listener for properties */ private PropL propertyChangeListener = null; /** is synchronization of name in progress */ private boolean synchronizeName; // init .................................................................................................................. /** * Constructs a node for a JavaBean. If the bean is a {@link BeanContext}, * creates a child list as well. * * @param bean the bean this node will be based on * @throws IntrospectionException if the bean cannot be analyzed */ public BeanNode (Object bean) throws IntrospectionException { this ( bean, getChildren (bean) ); } /** Constructs a node for a JavaBean with a defined child list. * Intended for use by subclasses with different strategies for computing the children. * @param bean the bean this node will be based on * @param children children for the node * @throws IntrospectionException if the bean cannot be analyzed */ protected BeanNode (Object bean, Children children) throws IntrospectionException { super (children); this.bean = bean; initialization (); } /** Set whether or not to keep the node name and Bean name synchronized automatically. * If enabled, the node will listen to changes in the name of the bean * and update the (system) name of the node appropriately. The name of the bean can * be obtained by calling <code>getName ()</code>, <code>getDisplayName ()</code> or from {@link BeanDescriptor#getDisplayName}. * <p>Also when the (system) name of the node is changing, the change propagates if possible to * methods <code>setName (String)</code> or <code>setDisplayName (String)</code>. (This * does not apply to setting the display name of the node, however.) * <P> * By default this feature is turned on. * * @param watch <code>true</code> if the name of the node should be synchronized with * the name of the bean, <code>false</code> if the name of the node should be independent * or manually updated * */ protected void setSynchronizeName (boolean watch) { synchronizeName = watch; } /** Provides access to the bean represented by this BeanNode. * @return instance of the bean represented by this BeanNode */ protected Object getBean () { return bean; } /** Detaches all listeners from the bean and destroys it. * @throws IOException if there was a problem */ public void destroy () throws IOException { if (removePCLMethod != null) { try { Object o = Beans.getInstanceOf (bean, removePCLMethod.getDeclaringClass ()); removePCLMethod.invoke (o, new Object[] {propertyChangeListener}); } catch (IllegalAccessException ee) { } catch (IllegalArgumentException ee) { } catch (InvocationTargetException ee) { } } super.destroy (); } /** Can this node be removed? * @return <CODE>true</CODE> in this implementation */ public boolean canDestroy () { return true; } /** Set the node name. * Also may attempt to change the name of the bean, * according to {@link #setSynchronizeName}. * @param s the new name */ public void setName (String s) { if (synchronizeName) { Method m = nameSetter; if (m != null) { try { m.invoke (bean, new Object[] {s}); } catch (Exception ex) {} } } super.setName (s); } /** Can this node be renamed? * @return <code>true</code> in this implementation */ public boolean canRename () { return true; } /** Get an icon for this node in the closed state. * Uses the Bean's icon if possible. * * @param type constant from {@link java.beans.BeanInfo} * @return icon to use */ public Image getIcon (int type) { Image image = beanInfo.getIcon (type); if (image != null) return image; return super.getIcon(type); } /** Get an icon for this node in the open state. * * @param type type constants * @return icon to use. The default implementation just uses {@link #getIcon}. */ public Image getOpenedIcon (int type) { return getIcon(type); } public HelpCtx getHelpCtx () { HelpCtx test = InstanceSupport.findHelp ((InstanceCookie) getCookie (InstanceCookie.class)); if (test != null) return test; else return new HelpCtx (BeanNode.class); } /** Prepare node properties based on the bean, storing them into the current property sheet. * Called when the bean info is ready. * This implementation always creates a set for standard properties * and may create a set for expert ones if there are any. * @see #computeProperties * @param bean bean to compute properties for * @param info information about the bean */ protected void createProperties (Object bean, BeanInfo info) { Descriptor d = computeProperties (bean, beanInfo); Sheet sets = getSheet (); Sheet.Set pset = Sheet.createPropertiesSet (); pset.put (d.property); sets.put (pset); if (d.expert.length != 0) { Sheet.Set eset = Sheet.createExpertSet (); eset.put (d.expert); sets.put (eset); } } /** Can this node be copied? * @return <code>true</code> in the default implementation */ public boolean canCopy () { return true; } /** Can this node be cut? * @return <code>false</code> in the default implementation */ public boolean canCut () { return false; } /* Getter for set of actions that should be present in the * popup menu of this node. This set is used in construction of * menu returned from getContextMenu and specially when a menu for * more nodes is constructed. * * @return array of system actions that should be in popup menu */ protected SystemAction[] createActions () { return new SystemAction[] { SystemAction.get (CustomizeBeanAction.class), null, SystemAction.get (CopyAction.class), null, SystemAction.get (ToolsAction.class), SystemAction.get (PropertiesAction.class) }; } /* Test if there is a customizer for this node. If <CODE>true</CODE> * the customizer can be obtained via <CODE>getCustomizer</CODE> method. * * @return <CODE>true</CODE> if there is a customizer. */ public boolean hasCustomizer () { // true if we have already computed beanInfo and it has customizer class return beanInfo.getBeanDescriptor ().getCustomizerClass () != null; } /* Returns the customizer component. * @return the component or <CODE>null</CODE> if there is no customizer */ public java.awt.Component getCustomizer () { Class clazz = beanInfo.getBeanDescriptor ().getCustomizerClass (); if (clazz == null) return null; Object o; try { o = clazz.newInstance (); } catch (InstantiationException e) { return null; } catch (IllegalAccessException e) { return null; } if (!(o instanceof java.awt.Component) || !(o instanceof java.beans.Customizer)) return null; Customizer cust = ((java.beans.Customizer)o); // if the customizer worries about the node // inform it if (cust instanceof NodeCustomizer) { ((NodeCustomizer)cust).attach (this); } cust.setObject (bean); if (removePCLMethod == null) { cust.addPropertyChangeListener ( new PropertyChangeListener () { public void propertyChange(PropertyChangeEvent e) { firePropertyChange ( e.getPropertyName (), e.getOldValue (), e.getNewValue () ); } }); } return (java.awt.Component)o; } /** Computes a descriptor for properties from a bean info. * * @param bean bean to create properties for * @param info about the bean * @return three property lists */ public static Descriptor computeProperties (Object bean, BeanInfo info) { ArrayList property = new ArrayList (); ArrayList expert = new ArrayList (); ArrayList hidden = new ArrayList (); PropertyDescriptor[] propertyDescriptor = info.getPropertyDescriptors (); int k = propertyDescriptor.length; for (int i = 0; i < k; i ++) { Node.Property prop; if (propertyDescriptor[i] instanceof IndexedPropertyDescriptor) { IndexedPropertyDescriptor p = (IndexedPropertyDescriptor) propertyDescriptor [i]; IndexedPropertySupport support = new IndexedPropertySupport ( bean, p.getPropertyType (), p.getIndexedPropertyType(), p.getReadMethod (), p.getWriteMethod (), p.getIndexedReadMethod (), p.getIndexedWriteMethod () ); support.setName (p.getName ()); support.setDisplayName (p.getDisplayName ()); support.setShortDescription (p.getShortDescription ()); prop = support; } else { PropertyDescriptor p = propertyDescriptor [i]; PropertySupport.Reflection support = new PropertySupport.Reflection ( bean, p.getPropertyType (), p.getReadMethod (), p.getWriteMethod () ); support.setName (p.getName ()); support.setDisplayName (p.getDisplayName ()); support.setShortDescription (p.getShortDescription ()); support.setPropertyEditorClass (p.getPropertyEditorClass ()); prop = support; } if (propertyDescriptor[i].isHidden ()) { // hidden property hidden.add (prop); } else { if (propertyDescriptor[i].isExpert ()) { expert.add (prop); } else { property.add (prop); } } }// for return new Descriptor (property, expert, hidden); } // // // Initialization methods // // /** Performs initalization of the node */ private void initialization () throws IntrospectionException { setIconBase (ICON_BASE); setDefaultAction (SystemAction.get (PropertiesAction.class)); setSynchronizeName (true); // Find the first public superclass of the actual class. // Should not introspect on a private class, because then the method objects // used for the property descriptors will not be callable without an // IllegalAccessException, even if overriding a public method from a public superclass. Class clazz = bean.getClass (); while (! Modifier.isPublic (clazz.getModifiers ())) { clazz = clazz.getSuperclass (); if (clazz == null) clazz = Object.class; // in case it was an interface } beanInfo = Utilities.getBeanInfo (clazz); // resolving the name of this bean BeanDescriptor descriptor = beanInfo.getBeanDescriptor (); setShortDescription (descriptor.getShortDescription ()); registerName (); setNameSilently (getNameForBean ()); // add propertyChangeListener EventSetDescriptor[] eventSetDescriptors = beanInfo.getEventSetDescriptors(); int i, k = eventSetDescriptors.length; Method method = null; for (i = 0; i < k; i++) { method = eventSetDescriptors [i].getAddListenerMethod (); if ((method != null) && (method.getName ().equals ("addPropertyChangeListener"))) // NOI18N break; } if (i != k) { try { Object o = Beans.getInstanceOf (bean, method.getDeclaringClass ()); propertyChangeListener = new PropL (); method.invoke (o, new Object[] { WeakListener.propertyChange (propertyChangeListener, o) }); removePCLMethod = eventSetDescriptors [i].getRemoveListenerMethod (); } catch (IllegalAccessException ee) { } catch (IllegalArgumentException ee) { } catch (InvocationTargetException ee) {} } createProperties (bean, beanInfo); getCookieSet ().add (new InstanceSupport.Instance (bean)); } // name resolving methods /** * Finds setter and getter methods for the name of the bean. Resisters listener * for changing of name. */ private void registerName () { // [PENDING] ought to use introspection, rather than look up the methods by name --jglick Class clazz = bean.getClass (); Class[] param = new Class [0]; // find getter for the name try { nameGetter = clazz.getMethod ("getName", param); // NOI18N } catch (Exception e) { try { nameGetter = clazz.getMethod ("getDisplayName", param); // NOI18N } catch (Exception ee) { nameGetter = null; return; } } // this code tests wheter everything is fine and the getter is // invokable try { nameGetter.invoke (bean, null); } catch (Exception e) { nameGetter = null; return; } // find the setter for the name param = new Class[] {String.class}; try { // tries to find method setName (String) nameSetter = clazz.getMethod ("setName", param); // NOI18N } catch (Exception e) { try { nameSetter = clazz.getMethod ("setDisplayName", param); // NOI18N } catch (Exception ee) { nameSetter = null; } } } /** * Returns name of the bean. */ private String getNameForBean () { try { if (nameGetter != null) { return (String)nameGetter.invoke (bean, null); } } catch (Exception ex) { } // it should not fail but if it fails then return toString value BeanDescriptor descriptor = beanInfo.getBeanDescriptor (); return descriptor.getDisplayName (); } /** To allow innerclasses to access the super.setName method. */ void setNameSilently (String name) { super.setName (name); } /** Descriptor of three types of properties. Regular, * expert and hidden. */ public static final class Descriptor extends Object { /** Regular properties. */ public final Node.Property[] property; /** Expert properties. */ public final Node.Property[] expert; /** Hidden properties. */ public final Node.Property[] hidden; /** private constructor */ Descriptor (ArrayList p, ArrayList e, ArrayList h) { property = new Node.Property[p.size ()]; p.toArray (property); expert = new Node.Property[e.size ()]; e.toArray (expert); hidden = new Node.Property[h.size ()]; h.toArray (hidden); } } /** Property change listener to update the properties of the node and * also the name of the node (sometimes) */ private final class PropL extends Object implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent e) { firePropertyChange (e.getPropertyName (), e.getOldValue (), e.getNewValue ()); if (synchronizeName) { String name = e.getPropertyName (); if (name == null || name.equals ("name") || name.equals ("displayName")) { // NOI18N String newName = getNameForBean (); if (!newName.equals (getName ())) { setNameSilently (newName); } } } } } } /* * Log * 30 Gandalf 1.29 1/12/00 Jesse Glick NOI18N * 29 Gandalf 1.28 11/5/99 Jaroslav Tulach WeakListener has now * registration methods. * 28 Gandalf 1.27 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 27 Gandalf 1.26 7/21/99 Ian Formanek Added protected method * getBean to provide access to the bean represented by the BeanNode * 26 Gandalf 1.25 6/30/99 Ian Formanek Reflecting package * change of NodeCustomizer * 25 Gandalf 1.24 6/25/99 Jesse Glick Instances can have * sensible help contexts. * 24 Gandalf 1.23 6/9/99 Ian Formanek ToolsAction * 23 Gandalf 1.22 6/8/99 Ian Formanek Minor changes * 22 Gandalf 1.21 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 21 Gandalf 1.20 6/3/99 Jaroslav Tulach NodePropertyEditor & * NodeCustomizer * 20 Gandalf 1.19 5/27/99 Jesse Glick [JavaDoc] * 19 Gandalf 1.18 5/27/99 Jaroslav Tulach Executors rearanged. * 18 Gandalf 1.17 5/26/99 Jesse Glick ST#1937 -- only * introspecting on public superclasses. * 17 Gandalf 1.16 5/9/99 Ian Formanek Fixed last change * 16 Gandalf 1.15 5/8/99 Ian Formanek Uses createPropertiesSet * and cretaeExpertSet from Sheet * 15 Gandalf 1.14 4/9/99 Ian Formanek Removed debug printlns * 14 Gandalf 1.13 3/29/99 Jesse Glick [JavaDoc] * 13 Gandalf 1.12 3/27/99 Jaroslav Tulach Support for serializing * beans into folder + implemented for control panel and repository * 12 Gandalf 1.11 3/26/99 Ian Formanek Fixed use of obsoleted * NbBundle.getBundle (this) * 11 Gandalf 1.10 3/18/99 Jesse Glick [JavaDoc] * 10 Gandalf 1.9 3/17/99 Jesse Glick [JavaDoc] * 9 Gandalf 1.8 3/17/99 Jesse Glick [JavaDoc] * 8 Gandalf 1.7 3/16/99 Jesse Glick [JavaDoc] and very minor * code changes. * 7 Gandalf 1.6 3/6/99 David Simonek * 6 Gandalf 1.5 2/25/99 Jaroslav Tulach Change of clipboard * management * 5 Gandalf 1.4 2/4/99 Petr Hamernik changes to be compiled * by jikes * 4 Gandalf 1.3 1/7/99 Ian Formanek fixed resource names * 3 Gandalf 1.2 1/6/99 Jan Jancura * 2 Gandalf 1.1 1/6/99 Jaroslav Tulach Change of package of * DataObject * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ * Beta Change History: * 0 Tuborg 0.17 --/--/98 Jan Jancura PanContextSupportAdded * 0 Tuborg 0.24 --/--/98 Jan Formanek bugfix (icon name) * 0 Tuborg 0.26 --/--/98 Jan Formanek ClipboardOperation.NONE employed * 0 Tuborg 0.27 --/--/98 Jan Formanek reflecting changes in PropertySupport * 0 Tuborg 0.28 --/--/98 Ales Novak Clipboardoperation, Menu * 0 Tuborg 0.29 --/--/98 Jaroslav Tulach Clipboard operations moved to the node * 0 Tuborg 0.30 --/--/98 Jan Jancura Get customizer repaired * 0 Tuborg 0.31 --/--/98 Jan Formanek Now also sets the expert and hidden flags for the * 0 Tuborg 0.31 --/--/98 Jan Formanek properties from reflection * 0 Tuborg 0.32 --/--/98 Jan Jancura bugfix * 0 Tuborg 0.34 --/--/98 Jan Jancura setParent, setBean, ()constructor for serialization * 0 Tuborg 0.35 --/--/98 Jaroslav Tulach added getNewTypes method * 0 Tuborg 0.37 --/--/98 Jan Jancura PropertySet * 0 Tuborg 0.40 --/--/98 Jan Formanek changed the stupid context menu * 0 Tuborg 0.41 --/--/98 Jan Formanek does not show the expert tab if there are no expert properties */